//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#include "ImageLoader.h"
#include <IL/IL.h>
//#include <IL/ILU.h>

#pragma comment (lib, "DevIL.lib")
//#pragma comment (lib, "ILU.lib")

namespace Framework
{

	//
	// IL Image
	//

	struct ILImage
	{
		ILuint	id;
		///
		ILImage()	{ id = ilGenImage(); }
		~ILImage()	{ ilDeleteImage( id ); }

		void Bind() const							{ ilBindImage( id ); }
		void Unbind() const							{ ilBindImage( 0 ); }
		bool ActiveMipmap(uint i) const				{ return ilActiveMipmap( i ) == IL_TRUE; }
		bool ActiveLayer(uint i) const				{ return ilActiveLayer( i ) == IL_TRUE; }
		bool ActiveFace(uint i) const				{ return ilActiveFace( i ) == IL_TRUE; }

		bool Load(uni_c_string filename) const		{ return ilLoadImage( filename.cstr() ) == IL_TRUE; }

		const void *			GetData() const		{ return ilGetData(); }
		gl_tex_iformat::type	IFormat() const		{ return (gl_tex_iformat::type) ilGetInteger(IL_IMAGE_FORMAT); }
		gl_pixel_format::type	Format() const		{ return (gl_pixel_format::type) ilGetInteger(IL_IMAGE_FORMAT); }
		gl_pixel::type			Type() const		{ return (gl_pixel::type) ilGetInteger(IL_IMAGE_TYPE); }
		uint					NumMipmaps() const	{ return ilGetInteger(IL_NUM_MIPMAPS); }
		uint					NumFaces() const	{ return ilGetInteger(IL_NUM_FACES); }
		uint					NumLayers() const	{ return ilGetInteger(IL_NUM_LAYERS); }
		const uvec3				Dimension2D() const	{ return uvec3( ilGetInteger(IL_IMAGE_WIDTH),
																	ilGetInteger(IL_IMAGE_HEIGHT), 0 ); }
		const uvec3				Dimension3D() const	{ return uvec3( ilGetInteger(IL_IMAGE_WIDTH),
																	ilGetInteger(IL_IMAGE_HEIGHT),
																	ilGetInteger(IL_IMAGE_DEPTH) ); }
	};

	
/*
=================================================
	InitDevIL
=================================================
*/
	static void InitDevIL ()
	{
		static bool		inited = false;

		if ( not inited )
		{
			inited = true;
			ilInit();
			//iluInit();
		}
	}

/*
=================================================
	Load
=================================================
*/
	bool ImageLoader::Load (GLTexture2DPtr &tex, uni_c_string filename, bool genMipmaps)
	{
		InitDevIL();

		ILImage	img;

		img.Bind();
		CHECK_RETURN( img.Load( filename ) );

		if ( tex.IsNotNull() )
			tex->Destroy();
		else
			tex = new GLTexture2D();

		const uint	mipmaps = img.NumMipmaps();
		
		CHECK_RETURN( tex->SetImage( img.IFormat(), img.Dimension2D(), img.Format(), img.Type(), img.GetData(), 0 ) );

		for (uint i = 1; i < mipmaps; ++i)
		{
			img.ActiveMipmap( i );
			CHECK_RETURN( tex->SetImage( img.IFormat(), img.Dimension2D(), img.Format(), img.Type(), img.GetData(), i ) );
		}

		if ( genMipmaps and mipmaps == 0 )
			CHECK_RETURN( tex->GenerateMipmaps() );

		return true;
	}
	
/*
=================================================
	Load
=================================================
*/
	bool ImageLoader::Load (GLTextureCubePtr &tex, uni_c_string filename, bool genMipmaps)
	{
		InitDevIL();

		ILImage	img;

		img.Bind();
		CHECK_RETURN( img.Load( filename ) );

		uint	faces = img.NumFaces();
		CHECK_VALUE( faces == 6 );
		

		if ( tex.IsNotNull() )
			tex->Destroy();
		else
			tex = new GLTextureCube();

		uint	min_mipmaps = -1;

		for (uint i = 0; i < faces; ++i)
		{
			img.ActiveFace( i );

			const uint	mipmaps = img.NumMipmaps();

			min_mipmaps = Min( min_mipmaps, mipmaps );
			
			CHECK_RETURN( tex->SetImage( img.IFormat(), uvec3( img.Dimension2D().xy(), i ),
											img.Format(), img.Type(), img.GetData(), 0 ) );

			for (uint j = 1; j < mipmaps; ++j)
			{
				img.ActiveMipmap( j );
				CHECK_RETURN( tex->SetImage( img.IFormat(), uvec3( img.Dimension2D().xy(), i ),
												img.Format(), img.Type(), img.GetData(), j ) );
			}
		}

		if ( genMipmaps and min_mipmaps == 0 )
			CHECK_RETURN( tex->GenerateMipmaps() );

		return true;
	}
	
/*
=================================================
	Load
=================================================
*/
	bool ImageLoader::Load (GLTextureCubePtr &tex,
							uni_c_string positiveX, uni_c_string negativeX,
							uni_c_string positiveY, uni_c_string negativeY,
							uni_c_string positiveZ, uni_c_string negativeZ,
							bool genMipmaps)
	{
		InitDevIL();

		const char *	files[6] = { positiveX.cstr(), negativeX.cstr(), positiveY.cstr(),
									 negativeY.cstr(), positiveZ.cstr(), negativeZ.cstr() };
		ILImage			img[6];
		uvec2			size;

		for (uint i = 0; i < 6; ++i)
		{
			img[i].Bind();
			CHECK_RETURN( img[i].Load( files[i] ) );

			if ( i == 0 )	size = img[i].Dimension2D().xy();
			else			CHECK_VALUE( All( size == img[i].Dimension2D().xy() ) );
		}
		

		if ( tex.IsNotNull() )
			tex->Destroy();
		else
			tex = new GLTextureCube();
		
		uint	min_mipmaps = 0;

		for (uint i = 0; i < 6; ++i)
		{
			img[i].Bind();

			const uint mipmaps = img[i].NumMipmaps();

			min_mipmaps = Min( min_mipmaps, mipmaps );
			
			CHECK_RETURN( tex->SetImage( img[i].IFormat(), uvec3( img[i].Dimension2D().xy(), i ),
										  img[i].Format(), img[i].Type(), img[i].GetData(), 0 ) );

			for (uint j = 1; j < mipmaps; ++j)
			{
				img[i].ActiveMipmap( j );
				CHECK_RETURN( tex->SetImage( img[i].IFormat(), uvec3( img[i].Dimension2D().xy(), i ),
											  img[i].Format(), img[i].Type(), img[i].GetData(), j ) );
			}
		}

		if ( genMipmaps and min_mipmaps == 0 )
			CHECK_RETURN( tex->GenerateMipmaps() );

		return true;
	}

/*
=================================================
	Load
=================================================
*/
	bool ImageLoader::Load (GLTexture2DArrayPtr &tex, const uni_array< uni_c_string> &filenames, bool genMipmaps)
	{
		InitDevIL();
		
		if ( tex.IsNotNull() )
			tex->Destroy();
		else
			tex = new GLTexture2DArray();


		ILImage	img;
		uint	min_mipmaps = 0;

		FOR( i, filenames )
		{
			img.Bind();
			CHECK_RETURN( img.Load( filenames[i] ) );
			
			img.ActiveMipmap( 0 );

			if ( i == 0 )
			{
				CHECK_RETURN( tex->SetImage( img.IFormat(), uvec3( img.Dimension2D().xy(), (uint)filenames.Count() ),
											 img.Format(), img.Type(), null, 0 ) );
			}

			const uint	mipmaps = img.NumMipmaps();

			min_mipmaps = Min( min_mipmaps, mipmaps );
			
			CHECK_RETURN( tex->SubImage( uvec3( 0, 0, uint(i) ), uvec3( img.Dimension2D().xy(), 1 ),
											img.Format(), img.Type(), img.GetData(), 0 ) );

			for (uint j = 1; j < mipmaps; ++j)
			{
				img.ActiveMipmap( j );
				CHECK_RETURN( tex->SubImage( uvec3( 0, 0, uint(i) ), uvec3( img.Dimension2D().xy(), 1 ),
												img.Format(), img.Type(), img.GetData(), j ) );
			}
		}

		if ( genMipmaps and min_mipmaps == 0 )
			CHECK_RETURN( tex->GenerateMipmaps() );

		return true;
	}

/*
=================================================
	Load
=================================================
*/
	bool ImageLoader::Load (GLTexture3DPtr &tex, uni_c_string filename, bool genMipmaps)
	{
		InitDevIL();
		
		if ( tex.IsNotNull() )
			tex->Destroy();
		else
			tex = new GLTexture3D();


		ILImage	img;

		img.Bind();
		CHECK_RETURN( img.Load( filename ) );

		uvec3	dim		= img.Dimension3D();
		CHECK_VALUE( All( dim > uvec3(1) ) );

		const uint	mipmaps	= img.NumMipmaps();
		
		CHECK_RETURN( tex->SetImage( img.IFormat(), img.Dimension3D(), img.Format(), img.Type(), img.GetData(), 0 ) );

		for (uint i = 1; i < mipmaps; ++i)
		{
			img.ActiveMipmap( i );
			CHECK_RETURN( tex->SetImage( img.IFormat(), img.Dimension3D(), img.Format(), img.Type(), img.GetData(), i ) );
		}

		if ( genMipmaps and mipmaps == 0 )
			CHECK_RETURN( tex->GenerateMipmaps() );

		return true;
	}

}	// Framework